<script setup lang="ts">
/**
 * @用途
 * @author bear
 * @创建时间 2025/10/27
 **/
import { computed, ref } from 'vue'
import { mdiImageBrokenVariant } from '@mdi/js'

const props = defineProps<{
  src: string
  maxWidth?: boolean // 宽度放大到最大
  selectable?: boolean // 是否开启框选
}>()
const emit = defineEmits<{
  (e: 'select', box: { x: number; y: number; w: number; h: number }): void
  (e: 'save', val: number): void
  (e: 'loaded'): void
}>()

const imgview = ref<HTMLImageElement | null>(null)
const imgBoxEl = ref<HTMLDivElement | null>(null)
const translate = reactive({ x: 0, y: 0 })
const rotation = ref(0) // 角度：0/90/180/270
const scale = ref(1)
const naturalWidth = ref<number>(0)
const naturalHeight = ref<number>(0)
const isPanning = ref(false)
const startX = ref(0)
const startY = ref(0)
const box = reactive({ x: 0, y: 0, ex: 0, ey: 0 })
const error = ref(false)
const rotation_show = ref(false)

const imgStyle = computed(() => {
  return {
    transform: `translate(${translate.x}px, ${translate.y}px) scale(${scale.value}) rotate(${rotation.value}deg)`
  }
})
const boxStyle = computed(() => {
  const { x, y, ex, ey } = box,
    b = { x: Math.min(x, ex), y: Math.min(y, ey), w: Math.abs(ex - x), h: Math.abs(ey - y) }

  return {
    width: `${b.w}px`,
    height: `${b.h}px`,
    left: `${b.x}px`,
    top: `${b.y}px`
  }
})

function mousedown(e: MouseEvent) {
  if (e.button !== 0) return
  isPanning.value = true
  if (props.selectable) {
    box.x = e.clientX
    box.y = e.clientY
  } else {
    startX.value = e.clientX - translate.x
    startY.value = e.clientY - translate.y
  }
  const onMove = (evt: MouseEvent) => {
    if (!isPanning.value) return
    if (props.selectable) {
      // 检测并设置结束坐标
      setSelect(evt)
    } else {
      translate.x = evt.clientX - startX.value
      translate.y = evt.clientY - startY.value
    }
  }
  const onUp = () => {
    isPanning.value = false
    if (props.selectable) selectBox()
    window.removeEventListener('mousemove', onMove)
    window.removeEventListener('mouseup', onUp)
  }
  window.addEventListener('mousemove', onMove)
  window.addEventListener('mouseup', onUp)
}

function setSelect(e: MouseEvent) {
  const img = imgview.value!
  if (img) {
    const rect = img.getBoundingClientRect()
    if (e.clientX < rect.left) box.ex = rect.left
    else if (e.clientX > rect.right) box.ex = rect.right
    else box.ex = e.clientX
    if (e.clientY < rect.top) box.ey = rect.top
    box.ey = Math.min(e.clientY, rect.bottom)
  }
}

function selectBox() {
  if (Number.parseInt(boxStyle.value.width) && Number.parseInt(boxStyle.value.height)) {
    // console.log('box', boxStyle.value, scale.value, translate)
    const x = (Number.parseInt(boxStyle.value.left) + translate.x) / scale.value,
      y = (Number.parseInt(boxStyle.value.top) + translate.y) / scale.value,
      w = Number.parseInt(boxStyle.value.width) / scale.value,
      h = Number.parseInt(boxStyle.value.height) / scale.value
    // console.log('选中区域', x, y, w, h, naturalWidth.value, naturalHeight.value)
    if (w > 10 && h > 10) emit('select', { x, y, w, h })
    box.x = 0
    box.y = 0
    box.ex = 0
    box.ey = 0
  }
}

function zoomIn() {
  scale.value = Math.min(5, scale.value * 1.1)
}

function zoomOut() {
  scale.value = Math.max(0.05, scale.value / 1.1)
}

// function rotateLeft() {
//   rotation.value = (rotation.value + 270) % 360
// }

function rotateRight() {
  rotation.value = (rotation.value + 90) % 360
  rotation_show.value = Boolean(rotation.value)
}

function mousewheel(e: WheelEvent) {
  if (e.deltaY > 0) zoomOut()
  else zoomIn()
}

function imgLoad() {
  const img = imgview.value!
  naturalWidth.value = img.naturalWidth
  naturalHeight.value = img.naturalHeight
  img.width = img.naturalWidth
  img.height = img.naturalHeight
  error.value = false
  fitToWindow()
  // 触发加载完成事件
  emit('loaded')
}

function fitToWindow() {
  const el = imgBoxEl.value

  if (!el) return
  const imgW = naturalWidth.value
  const imgH = naturalHeight.value
  if (!imgW || !imgH) return
  const { clientWidth: cw, clientHeight: ch } = el
  const s = Math.min(cw / imgW, ch / imgH)
  // console.log('fitToWindow', s, cw, ch, imgW, imgH, cw / imgW, ch / imgH)
  scale.value = Math.max(0.05, Math.min(5, s))
  translate.x = 0
  translate.y = 0
}

function actualSize() {
  scale.value = 1
  translate.x = 0
  translate.y = 0
}

function resetView() {
  scale.value = 1
  rotation.value = 0
  translate.x = 0
  translate.y = 0
}

function saveImg() {
  emit('save', rotation.value)
  rotation.value = 0
}

function imgError() {
  error.value = true
}

defineExpose({
  fitToWindow,
  actualSize,
  zoomIn,
  zoomOut,
  resetView,
  rotateRight,
  rotation
})
</script>

<template>
  <div
    ref="imgBoxEl"
    class="w-100 d-flex justify-center align-center h-100 position-relative"
    @mousewheel.stop.prevent="mousewheel"
  >
    <img
      ref="imgview"
      alt=""
      class="flex-none imgview no-select"
      :src="src"
      :style="imgStyle"
      @error="imgError"
      @load="imgLoad"
      @mousedown.prevent="mousedown"
    />
    <div
      v-if="error"
      class="position-absolute top-0 left-0 w-100 h-100 d-flex align-center justify-center bg-background"
    >
      <v-icon :icon="mdiImageBrokenVariant" size="100" />
    </div>
    <slot></slot>
    <div
      v-if="selectable && box.x && box.ex"
      class="position-absolute select-box"
      :style="boxStyle"
    ></div>
    <v-snackbar v-if="rotation" v-model:model-value="rotation_show" color="primary" :timeout="-1">
      图片已旋转 {{ rotation }} 度
      <template #actions>
        <v-btn
          color="info"
          data-testid="image-preview-save-button"
          rounded="5px"
          variant="elevated"
          @click="saveImg"
          >保存</v-btn
        >
      </template>
    </v-snackbar>
  </div>
</template>

<style scoped>
.imgview {
  transition: transform 0.3s ease-out;
}

.select-box {
  background: rgba(0, 0, 0, 0.5);
  border: 1px dashed rgba(255, 255, 255, 0.5);
}
</style>
